package com.liuyjy.redis.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;

/**
 * @Author liuyjy
 * @Date 2019/10/15 9:53
 * @Description: Redis分布式锁
 **/
@Slf4j
@Component
public class RedisLockUtil {

    private static final int LOCK_EXPIRE = 6000;
    private static final String REDIS_LOCKKEY = "redisLock:";

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 加锁
     *
     * @param k    主键，用于构成锁的唯一标识的一部分
     * @param subK 子键，与主键一起使用以构成锁的唯一标识
     * @return 如果成功获取锁，返回true；否则返回false
     * @throws IllegalStateException 如果主键或子键为空，则抛出此异常
     */
    public boolean getLock(String k, String subK) {
        // 组合键和子键以形成完整的键
        String key = Utils.getKey(k, subK);
        return getLock(key);
    }

    /***
     * 加锁
     * @param key LockKey
     * @return 锁住返回true
     */
    public boolean getLock(String key) {
        if (StringUtils.isBlank(key)) {
            throw new IllegalStateException("key为空");
        }
        // 组合锁的键
        String lockKey = REDIS_LOCKKEY + key;

        byte[] lockKeyBytes = lockKey.getBytes();

        // 利用lambda表达式
        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {

            // 计算锁的过期时间
            long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
            byte[] expireAtBytes = String.valueOf(expireAt).getBytes();

            // 尝试设置锁，如果成功则返回true
            Boolean acquire = connection.setNX(lockKeyBytes, expireAtBytes);
            if (null == acquire) {
                return false;
            }
            // 如果获取锁失败，返回false
            if (acquire) {
                return true;
            }
            // 获取锁的当前值
            byte[] value = connection.get(lockKeyBytes);
            if (Objects.isNull(value) || value.length == 0) {
                return false;
            }

            long expireTime = Long.parseLong(new String(value));
            // 如果锁已经过期
            if (expireTime > System.currentTimeMillis()) {
                return false;
            }
            byte[] oldValue = connection.getSet(lockKeyBytes, expireAtBytes);
            return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
        });
    }

    /**
     * 解锁方法，用于释放之前锁定的键
     *
     * @param k    主键，用于标识资源或数据的主类别
     * @param subK 子键，用于进一步细化标识资源或数据的子类别
     * @throws IllegalStateException 如果主键或子键为空，则抛出此异常，因为Redis键不能为空
     */
    public void unlock(String k, String subK) {
        // 组合键和子键以形成完整的键
        String key = Utils.getKey(k, subK);
        unlock(key);
    }

    /***
     * 解锁
     * @param key LockKey
     */
    public void unlock(String key) {
        if (StringUtils.isBlank(key)) {
            throw new IllegalStateException("key为空");
        }
        String keys = REDIS_LOCKKEY + key;
        redisTemplate.delete(keys);
    }
}
